/*
 * (C) Copyright 2013 George Daswani (georgedaswani@gmail.com) and others.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Lesser General Public License
 * (LGPL) version 2.1 which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/lgpl-2.1.html
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * Contributors:
 *    George Daswani (georgedaswani@gmail.com)
 */

package am.api.wrapper;

import java.util.NoSuchElementException;

import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.pool.KeyedObjectPool;
import org.apache.commons.pool.impl.GenericKeyedObjectPool;
import org.apache.log4j.Logger;
import org.springframework.util.Assert;

import am.api.AMConnection;
import am.api.exception.AMConnectionException;
import am.api.model.AMCredential;
import am.api.util.NLS;

public class ConnectionPool {

	private final static Logger logger = Logger.getLogger(ConnectionPool.class);

	private KeyedObjectPool<AMCredential, AMConnection> pool;
	private AMCredential defaultCredential;
	private int maxActive = 10;
	private int maxIdle = 1;

	public ConnectionPool() {
		super();
	}

	public synchronized void init() {

		Assert.notNull(defaultCredential, "defaultCredential is required.");
		Assert.isTrue(maxActive > 0, "maxActive needs to be greater than 0.");
		Assert.isTrue(maxIdle > 0, "maxIdle needs to be greater than 0.");
		Assert.isTrue(maxActive > maxIdle,
				"maxActive needs to be higher than maxIdle.");

		pool = new GenericKeyedObjectPool<AMCredential, AMConnection>(
				new PooledConnectionFactory(this), maxActive,
				GenericKeyedObjectPool.WHEN_EXHAUSTED_FAIL, 0, maxIdle, true,
				false);

	}

	public synchronized void close() {
		try {
			pool.close();
		} catch (Exception error) {
			throw new IllegalStateException(error);
		}
	}

	public AMConnection getConnection(AMCredential credential)
			throws AMConnectionException {

		if (logger.isDebugEnabled()) {
			logger.debug(String.format("credential: %s", credential));
		}

		AMCredential credentialToLookup = (credential != null) ? credential
				: defaultCredential;

		AMConnection pConnection = null;

		try {
			pConnection = pool.borrowObject(credentialToLookup);
		} catch (NoSuchElementException e) {
			logger.warn(String.format("Pool Exhausted, message=[%s]",
					e.getMessage()));
			AMConnectionException connE = new AMConnectionException(
					NLS.ERRORS.getString("connection.cannot.borrow.exhausted"));
			connE.initCause(e);
			throw connE;
		} catch (IllegalStateException e) {
			logger.error(e);
			throw e;
		} catch (Exception e) {
			logger.error(String.format("Exception,  message=[%s]",
					e.getMessage()));
			AMConnectionException connE = new AMConnectionException(
					NLS.ERRORS.getString("connection.cannot.borrow.exception"));
			connE.initCause(e);
			throw connE;
		}

		return pConnection;
	}

	public AMCredential getDefaultCredential() {
		return defaultCredential;
	}

	public void setDefaultCredential(AMCredential defaultCredential) {
		this.defaultCredential = defaultCredential;
	}

	void invalidateObject(AMCredential credential, AMConnection connection) {

		if (logger.isDebugEnabled()) {
			logger.debug(String.format("credential: %s, connection: %s",
					credential, connection));
		}

		try {
			pool.invalidateObject(credential, connection);
		} catch (Exception e) {
			throw new IllegalStateException(e);
		}
	}

	void returnObject(AMCredential credential, AMConnection connection) {

		if (logger.isDebugEnabled()) {
			logger.debug(String.format("credential: %s, connection: %s",
					credential, connection));
		}

		try {
			pool.returnObject(credential, connection);
		} catch (Exception e) {
			throw new IllegalStateException(e);
		}
	}

	public int getMaxActive() {
		return maxActive;
	}

	public void setMaxActive(int maxActive) {
		this.maxActive = maxActive;
	}

	public int getMaxIdle() {
		return maxIdle;
	}

	public void setMaxIdle(int maxIdle) {
		this.maxIdle = maxIdle;
	}

	public String toString() {
		return new ToStringBuilder(this).append("pool", pool)
				.append("defaultCredential", defaultCredential)
				.append("maxActive", maxActive).append("maxIdle", maxIdle)
				.toString();
	}

	public void destroy() {

		if (pool != null) {
			close();
		}

		pool = null;
		defaultCredential = null;
		maxActive = 10;
		maxIdle = 1;

	}
}
